////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

package mx.formatters
{

import mx.core.mx_internal;
import mx.managers.ISystemManager;
import mx.managers.SystemManager;
import mx.resources.ResourceBundle;

/**
 *  The DateFormatter class uses a format String to render date and time Strings
 *  from a String or a Date object.
 *  You can create many variations easily, including international formats.
 *
 *  <p>If an error occurs, an empty String is returned and a String describing 
 *  the error is saved to the <code>error</code> property. The <code>error</code> 
 *  property can have one of the following values:</p>
 *
 *  <ul>
 *    <li><code>"Invalid value"</code> means a value that is not a Date object or a 
 *    is not a recognized String representation of a date is
 *    passed to the <code>format()</code> method. (An empty argument is allowed.)</li>
 *    <li> <code>"Invalid format"</code> means either the <code>formatString</code> 
 *    property is set to empty (""), or there is less than one pattern letter 
 *    in the <code>formatString</code> property.</li>
 *  </ul>
 *  
 *  @mxml
 *  
 *  <p>You use the <code>&lt;mx:DateFormatter&gt;</code> tag
 *  to render date and time Strings from a Date object.</p>
 *
 *  <p>The <code>&lt;mx:DateFormatter&gt;</code> tag
 *  inherits all of the tag attributes  of its superclass,
 *  and adds the following tag attributes:</p>
 *  
 *  <pre>
 *  &lt;mx:DateFormatter
 *    formatString="Y|M|D|A|E|H|J|K|L|N|S"
 *   /> 
 *  </pre>
 *  
 *  @includeExample examples/DateFormatterExample.mxml
 *  
 *  @see mx.formatters.DateBase
 */
public class DateFormatter extends Formatter
{
    include "../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Class initialization
	//
	//--------------------------------------------------------------------------

	loadResources();
	
	//--------------------------------------------------------------------------
	//
	//  Class constants
	//
	//--------------------------------------------------------------------------

    /**
	 *  @private    
     */
	private static const VALID_PATTERN_CHARS:String = "Y,M,D,A,E,H,J,K,L,N,S";
	
	//--------------------------------------------------------------------------
	//
	//  Class resources
	//
	//--------------------------------------------------------------------------
		
	[ResourceBundle("SharedResources")]

    /**
	 *  @private    
     */	
	private static var sharedResources:ResourceBundle;

    /**
	 *  @private    
     */	
	private static var resourceFormatString:String;

	//--------------------------------------------------------------------------
	//
	//  Class methods
	//
	//--------------------------------------------------------------------------

    /**
	 *  @private    
     *  Loads resources for this class.
     */
	private static function loadResources():void
	{
		resourceFormatString = sharedResources.getString("dateFormat");
	}
			
	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function DateFormatter()
	{
		super();

		bundleChanged();
	}

	//--------------------------------------------------------------------------
	//
	//  Properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  formatString
	//----------------------------------
	
	[Inspectable(category="General", defaultValue="MM/DD/YYYY")]

	/**
	 *  The mask pattern.
	 *  
	 *  <p>You compose a pattern String using specific uppercase letters,
	 *  for example: YYYY/MM.</p>
	 *
	 *  <p>The DateFormatter pattern String can contain other text
	 *  in addition to pattern letters.
	 *  To form a valid pattern String, you only need one pattern letter.</p>
	 *  	
	 *  <p>The following table describes the valid pattern letters:</p>
	 *
	 *  <table class="innertable">
	 *    <tr><th>Pattern letter</th><th>Description</th></tr>
	 *    <tr>
	 *      <td>Y</td>
	 *      <td> Year. If the number of pattern letters is two, the year is 
	 *        truncated to two digits; otherwise, it appears as four digits. 
	 *        The year can be zero-padded, as the third example shows in the 
	 *        following set of examples: <br></br>
	 *          YY = 05<br></br>
	 *          YYYY = 2005<br></br>
	 *          YYYYY = 02005</td>
	 *    </tr>
	 *    <tr>
	 *      <td>M</td>
	 *      <td> Month in year. The format depends on the following criteria:
	 *        <ul>
	 *          <li>If the number of pattern letters is one, the format is 
	 *            interpreted as numeric in one or two digits. </li>
	 *          <li>If the number of pattern letters is two, the format 
	 *            is interpreted as numeric in two digits.</li>
	 *          <li>If the number of pattern letters is three, 
	 *            the format is interpreted as short text.</li>
	 *          <li>If the number of pattern letters is four, the format 
	 *           is interpreted as full text. </li>
	 *        </ul>
	 *          Examples:<br></br>
	 *          M = 7<br></br>
	 *          MM= 07<br></br>
	 *          MMM=Jul<br></br>
	 *          MMMM= July</td>
	 *    </tr>
	 *    <tr>
	 *      <td>D</td>
	 *      <td>Day in month. While a single-letter pattern string for day is valid, 
	 *        you typically use a two-letter pattern string.<br></br>
	 *          Examples:<br></br>
	 *          D=4<br></br>
	 *          DD=04<br></br>
	 *          DD=10</td>
	 *    </tr>
	 *    <tr>
	 *      <td>E</td>
	 *      <td>Day in week. The format depends on the following criteria:
	 *        <ul>
	 *          <li>If the number of pattern letters is one, the format is 
	 *            interpreted as numeric in one or two digits.</li>
	 *          <li>If the number of pattern letters is two, the format is interpreted 
	 *           as numeric in two digits.</li>
	 *          <li>If the number of pattern letters is three, the format is interpreted 
	 *            as short text. </li>
	 *          <li>If the number of pattern letters is four, the format is interpreted 
	 *           as full text. </li>
	 *        </ul>
	 *          Examples:<br></br>
	 *          E = 1<br></br>
	 *          EE = 01<br></br>
	 *          EEE = Mon<br></br>
	 *          EEEE = Monday</td>
	 *    </tr>
	 *    <tr>
	 *      <td>A</td>
	 *      <td> am/pm indicator.</td>
	 *    </tr>
	 *    <tr>
	 *      <td>J</td>
	 *      <td>Hour in day (0-23).</td>
	 *    </tr>
	 *    <tr>
	 *      <td>H</td>
	 *      <td>Hour in day (1-24).</td>
	 *    </tr>
	 *    <tr>
	 *      <td>K</td>
	 *      <td>Hour in am/pm (0-11).</td>
	 *    </tr>
	 *    <tr>
	 *      <td>L</td>
	 *      <td>Hour in am/pm (1-12).</td>
	 *    </tr>
	 *    <tr>
	 *      <td>N</td>
	 *      <td>Minute in hour. <br></br>
	 *          Examples:<br></br>
	 *          N = 3<br></br>
	 *          NN = 03</td>
	 *    </tr>
	 *    <tr>
	 *      <td>S</td>
	 *      <td>Second in minute. <br></br>
	 *          Examples:<br></br>
	 *          SS = 30</td>
	 *    </tr>
	 *    <tr>
	 *      <td>Other text</td>
	 *      <td>You can add other text into the pattern string to further 
	 *        format the string. You can use punctuation, numbers, 
	 *        and all lowercase letters. You should avoid uppercase letters 
	 *        because they may be interpreted as pattern letters.<br></br>
	 *          Example:<br></br>
	 *          EEEE, MMM. D, YYYY at H:NN A = Tuesday, Sept. 8, 2005 at 1:26 PM</td>
	 *    </tr>
	 *  </table>
	 *
	 *  @default "MM/DD/YYYY"
	 */
	public var formatString:String; 

	//--------------------------------------------------------------------------
	//
	//  Overridden methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  Formats a String as a date.
	 *  The <code>formatString</code> property
	 *  determines the format of the output String.
	 *  If <code>value</code> cannot be formatted, return an empty String 
	 *  and write a description of the error to the <code>error</code> property.
	 *
	 *  @param value Date to format. This can be a Date object,
	 *  or a date String such as "Thursday, April 22, 2004".
	 *
	 *  @return Formatted String. Empty if an error occurs. A description 
	 *  of the error condition is written to the <code>error</code> property.
	 */
	override public function format(value:Object):String
	{		
		// Reset any previous errors.
		if (error)
			error = null;

		// If value is null, or empty String just return "" 
		// but treat it as an error for consistency.
		// Users will ignore it anyway.
		if (!value || value == "")
		{
		    error = defaultInvalidValueError;
		    return "";
		}

		// -- value --

		if (value is String)
		{
			value = DateFormatter.parseDateString(String(value));
			if (!value)
			{
				error = defaultInvalidValueError;
				return "";
			}
		}
		else if (!(value is Date))
		{
            error = defaultInvalidValueError;
            return "";
		}

		// -- format --

		var letter:String;
		var nTokens:int = 0;
		var tokens:String = "";
		
		var n:int = formatString.length;
		for (var i:int = 0; i < n; i++)
		{
			letter = formatString.charAt(i);
			if (VALID_PATTERN_CHARS.indexOf(letter) != -1 && letter != ",")
			{
				nTokens++;
				if (tokens.indexOf(letter) == -1)
				{
					tokens += letter;
				}
				else
				{
					if (letter != formatString.charAt(Math.max(i - 1, 0)))
					{
						error = defaultInvalidFormatError;
						return "";
					}
				}
			}
		}

		if (nTokens < 1)
		{
			error = defaultInvalidFormatError;
			return "";
		}

		var dataFormatter:StringFormatter = new StringFormatter(
			formatString, VALID_PATTERN_CHARS, DateBase.mx_internal::extractTokenDate);

		return dataFormatter.formatValue(value);
	}

	//--------------------------------------------------------------------------
	//
	//  Methods
	//
	//--------------------------------------------------------------------------

    /**
	 *  @private    
     *  Populates localizable properties from the loaded bundle for this class.
     */
	private function bundleChanged():void
	{
		formatString = resourceFormatString;
	}
	
    /**
	 *  Converts a date that is formatted as a String into a Date object.
	 *  Month and day names must match the names in mx.formatters.DateBase.
	 *  
	 *  @see mx.formatters.DateBase
	 * 
     *  @param str Date that is formatted as a String. 
	 *
     *  @return Date object.
     */
    protected static function parseDateString (str:String):Date
    {
        if (!str || str == "")
			return null;

        var year:int = -1;
        var mon:int = -1;
        var day:int = -1;
        var hour:int = -1;
        var min:int = -1;
        var sec:int = -1;
        
		var letter:String = "";
        var marker:Object = 0;
        
        var count:int = 0;
		var len:int = str.length;
        
		while (count < len)
        {
            letter = str.charAt(count);
            count++;

            // If the letter is a blank space or a comma,
			// continue to the next character
            if (letter <= " " || letter == ",")
                continue;

            // If the letter is a key punctuation character,
			// cache it for the next time around.
            if (letter == "/" || letter == ":" ||
				letter == "+" || letter == "-")
            {
                marker = letter;
                continue;
            }

            // Scan for groups of numbers and letters
			// and match them to Date parameters
            if ("a" <= letter && letter <= "z" ||
				"A" <= letter && letter <= "Z")
			{
                // Scan for groups of letters
                var word:String = letter;
                while (count < len) 
				{
                    letter = str.charAt(count);
                    if (!("a" <= letter && letter <= "z" ||
						  "A" <= letter && letter <= "Z"))
					{
                        break;
					}
                    word += letter;
                    count++;
                }

                // Allow for an exact match
				// or a match to the first 3 letters as a prefix.
                var n:int = DateBase.mx_internal::defaultStringKey.length;
				for (var i:int = 0; i < n; i++)
                {
                    var s:String = String(DateBase.mx_internal::defaultStringKey[i]);
					if (s.toLowerCase() == word.toLowerCase() ||
                        s.toLowerCase().substr(0,3) == word.toLowerCase())
                    {
                        if (i == 13) 
						{
							// pm
                            if (hour > 12 || hour < 1)
                                break; // error
                            else if (hour < 12)
                                hour += 12;
                        } 
						else if (i == 12) 
						{
							// am
                            if (hour > 12 || hour < 1)
                                break; // error
                            else if (hour == 12)
                                hour = 0;

                        } 
						else if (i < 12) 
						{
							// month
                            if (mon < 0)
                                mon = i;
                            else
                                break; // error
                        }
                        break;
                    }
                }
                marker = 0;
            }
            
			else if ("0" <= letter && letter <= "9")
			{
                // Scan for groups of numbers
                var numbers:String = letter;
                while ("0" <= (letter = str.charAt(count)) &&
					   letter <= "9" &&
					   count < len)
				{
                    numbers += letter;
                    count++;
                }
                var num:int = int(numbers);

                // If num is a number greater than 70, assign num to year.
                if (num >= 70)
				{
                    if (year != -1)
					{
                        break; // error
					}
                    else if (letter <= " " || letter == "," || letter == "." ||
                             letter == "/" || letter == "-" || count >= len)
					{
                        year = num;
					}
                    else
					{
                        break; //error
					}
				}

                // If the current letter is a slash or a dash,
				// assign num to month or day.
				else if (letter == "/" || letter == "-" || letter == ".")
				{
		            if (mon < 0)
                        mon = (num - 1);
                    else if (day < 0)
                        day = num;
                    else
                        break; //error
                }

                // If the current letter is a colon,
				// assign num to hour or minute.
				else if (letter == ":")
				{
                    if (hour < 0)
                        hour = num;
                    else if (min < 0)
                        min = num;
                    else
                        break; //error
                }

                // If hours are defined and minutes are not,
				// assign num to minutes.
				else if (hour >= 0 && min < 0)
				{
                    min = num;
                }

                // If minutes are defined and seconds are not,
				// assign num to seconds.
				else if (min >= 0 && sec < 0)
				{
                    sec = num;
                }

                // If day is not defined, assign num to day.
				else if (day < 0)
				{
                    day = num;
                }

                // If month and day are defined and year is not,
				// assign num to year.
				else if (year < 0 && mon >= 0 && day >= 0)
				{
                    year = 2000 + num;
                }

                // Otherwise, break the loop
				else
				{
                    break;  //error
                }
                
				marker = 0
            }
        }

        if (year < 0 || mon < 0 || mon > 11 || day < 1 || day > 31)
            return null; // error - needs to be a date

        // Time is set to 0 if null.
        if (sec < 0)
			sec = 0;
        if (min < 0)
			min = 0;
        if (hour < 0)
			hour = 0;

		// create a date object and check the validity of the input date
		// by comparing the result with input values.
        var newDate:Date = new Date(year, mon, day, hour, min, sec);
		if (day != newDate.getDate() || mon != newDate.getMonth())
			return null;

        return newDate;
    }
}

}
